package com.tssup.fesco.common.utils;

import com.google.common.collect.Sets;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Set;

/**
 * Created by dengcao.
 *
 * usage: dynamic add new properties or interfaces for a Bean object that form penguin or other sdk,
 *        what you need to do is to extend the Old Bean, then if you invoke the old interface , the action will
 *        be delegated to the old Bean object, so the benefit is you need not to overwrite the old interface.
 */
public class WrapperUtils {
    private static final Logger logger = LoggerFactory.getLogger(WrapperUtils.class);
    public static class WrapperBuilder<T> {
        private Class<T> wrapper;
        private Object wrappeeObject;

        public WrapperBuilder<T> wrapper(Class<T> clazz) {
            this.wrapper = clazz;
            return this;
        }

        public WrapperBuilder<T> wrappeeObject(Object wrappeeObject) {
            this.wrappeeObject = wrappeeObject;
            return this;
        }

        public T build() {
            checkParams();
            T result = new CGlibProxy<T>(wrapper, wrappeeObject).getProxy();
            return result;
        }

        private void checkParams() {
            if(wrappeeObject == null) {
                logger.error("MapperUtils Error: wrappeeObject is required!");
                throw new IllegalArgumentException("MapperUtils Error: wrappeeObject is required!");
            }
        }
    }

    public static <T> WrapperBuilder<T> wrap(Class<T> wrapper) {
        WrapperBuilder<T> wrapperBuilder = new WrapperBuilder<T>();
        wrapperBuilder.wrapper = wrapper;
        return wrapperBuilder;
    }



    private static class CGlibProxy<T> implements MethodInterceptor {
        private Enhancer enhancer = new Enhancer();
        private Class<T> wrapper;
        private Object wrappeeObject;
        private Set<Method> wrapperMethods = Sets.newHashSet();

        public CGlibProxy(Class<T> wrapper,  Object wrappeeObject) {
            this.wrapper = wrapper;
            this.wrappeeObject = wrappeeObject;

            getWrapperMethods();
        }

        public T getProxy() {
            enhancer.setSuperclass(wrapper);
            enhancer.setCallback(this);
            return (T)enhancer.create();
        }


        public void getWrapperMethods() {
            Method[] methods = wrapper.getDeclaredMethods();
            for(Method method : methods) {
                if(Modifier.isPublic(method.getModifiers())) {
                    wrapperMethods.add(method);
                }
            }
        }


        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            if(wrapperMethods.contains(method)) {
                return methodProxy.invokeSuper(o, args);
            }
            return method.invoke(wrappeeObject, args);
        }
    }


}
